// Learn TypeScript:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/life-cycle-callbacks.html

import Coin from "./Coin";
import FadePanel from "./FadePanel";
import GameData, { Upgrades } from "./GameData";
import GameUI from "./GameUI";
import Guest from "./Guest";
import { PoolManager } from "./PoolManager";

const { ccclass, property } = cc._decorator;

@ccclass
export default class GameScene extends cc.Component {

    public static get Instance(): GameScene {
        return this.instance;
    }

    private static instance: GameScene;

    public set CutPotato(bool: boolean) {
        var ani = this.potato.getComponent(cc.Animation);
        if (this.cutPotato != bool) {
            if (this.cutTime < this.potatoTime) {
                var state = ani.play("Hand");
                if (bool) state.wrapMode = cc.WrapMode.Normal;
                else state.wrapMode = cc.WrapMode.Reverse;
            }
            else ani.play("Finish"), this.cutFinish = true;
        }
        this.cutPotato = bool;
    }

    @property([cc.Node])
    ingredients: cc.Node[] = [];

    @property([cc.Node])
    ingredientsAdd: cc.Node[] = [];

    @property([cc.Node])
    ingredientsNeed: cc.Node[] = [];

    @property([cc.Node])
    guestSummons: cc.Node[] = [];

    @property([cc.Node])
    guestWaits: cc.Node[] = [];

    @property([cc.Prefab])
    ingredientPres: cc.Prefab[] = [];

    @property([cc.Prefab])
    coinPres: cc.Prefab[] = [];

    @property(cc.Node)
    guestPanel: cc.Node = null;

    @property(cc.Node)
    guestWait: cc.Node;

    @property(cc.Node)
    bg: cc.Node = null;

    @property(cc.Node)
    meat: cc.Node = null;

    @property(cc.Node)
    knife: cc.Node = null;

    @property(cc.Node)
    assistant: cc.Node = null;

    @property(cc.Node)
    potato: cc.Node = null;

    @property(cc.Node)
    fryMech: cc.Node = null;

    @property(cc.Node)
    cakes: cc.Node = null;

    @property(cc.Node)
    panel: cc.Node = null;

    @property(cc.Node)
    package: cc.Node = null;

    @property(cc.Node)
    packTur: cc.Node = null;

    @property(cc.Node)
    sandwiches: cc.Node;

    @property(cc.Node)
    tutorial: cc.Node;

    @property(cc.Node)
    rightWall: cc.Node;

    @property(cc.Node)
    coinPanel: cc.Node = null;

    @property(cc.Prefab)
    pointPre: cc.Prefab = null;

    @property(cc.Prefab)
    cakePre: cc.Prefab = null;

    @property(cc.Prefab)
    packPre: cc.Prefab = null;

    @property(cc.Prefab)
    sandwithPre: cc.Prefab = null;

    @property(cc.Prefab)
    guestPres: cc.Prefab = null;

    @property(cc.Label)
    fryLabel: cc.Label = null;

    @property(cc.Label)
    dayLabel: cc.Label = null;

    @property(cc.Label)
    timeLabel: cc.Label = null;

    @property(cc.Sprite)
    cutProgress: cc.Sprite = null;

    guestDatas = [
        null,
        null,
        null,
        null
    ]

    ingredient = [0, 0, 0, 0];
    cakeItems = [0, 0, 0, 0];
    cakeItems2 = [0, 0, 0, 0];
    itemPart = [1, 4, 4, 20];

    max = 40;
    potatoTime = 6;
    fryTime = 12;
    guestLength = 3;
    dragLength = 0;
    cutTime = 0;
    fringTime = 0;
    guestNum = 0;
    time = 0;

    isCake = false;
    isIngredients = [false, false, false, false];
    isRoll = false;
    isPackage = false;
    callAss = false;
    cutPotato = false;
    cutFinish = false;
    fryReady = false;

    knifePos: cc.Vec2;
    sandwichPos: cc.Vec2;
    cake: cc.Node;
    pack: cc.Node;
    coins: cc.Node[] = [];

    // LIFE-CYCLE CALLBACKS:

    onLoad() {
        GameScene.instance = this;
        this.CheckUpgrades();
        cc.macro.ENABLE_MULTI_TOUCH = false;
        this.InitDrag();
        this.SetSibling();
        if (GameData.Instance.Datas[1] > 1)
            this.InitTime();
        else this.tutorial.active = true;
    }

    SetSibling() {
        this.cakes.setSiblingIndex(99999);
    }

    CheckUpgrades() {
        if (GameData.Instance.Upgrades.get(Upgrades.knife)[1]) {
            this.itemPart[0] = 2;
            this.knife.children[0].active = false;
            this.knife.children[1].active = true;
        }
        if (GameData.Instance.Upgrades.get(Upgrades.assistant)[1]) this.itemPart[1] = 8, this.itemPart[2] = 8;
        if (GameData.Instance.Upgrades.get(Upgrades.potato)[1]) this.potatoTime = 3;
        if (GameData.Instance.Upgrades.get(Upgrades.fryMach)[1]) this.fryTime = 6;
        if (GameData.Instance.Upgrades.get(Upgrades.FthGuest)[1]) this.rightWall.active = false, this.guestLength = 4;
    }

    InitDrag() {
        this.knifePos = this.knife.getPosition();
        this.knife.on(cc.Node.EventType.TOUCH_START, this.DragStart, this);
        this.knife.on(cc.Node.EventType.TOUCH_MOVE, this.DragMove, this);
        this.knife.on(cc.Node.EventType.TOUCH_END, this.DragEnd, this);
        this.knife.on(cc.Node.EventType.TOUCH_CANCEL, this.DragEnd, this);
        this.potato.on(cc.Node.EventType.TOUCH_START, this.DragStart, this);
        this.potato.on(cc.Node.EventType.TOUCH_CANCEL, this.DragEnd, this);
        this.potato.on(cc.Node.EventType.TOUCH_END, this.DragEnd, this);
        this.package.on(cc.Node.EventType.TOUCH_START, this.DragStart, this);
        this.package.on(cc.Node.EventType.TOUCH_MOVE, this.DragMove, this);
        this.package.on(cc.Node.EventType.TOUCH_END, this.DragEnd, this);
        this.package.on(cc.Node.EventType.TOUCH_CANCEL, this.DragEnd, this);
        this.coinPanel.on(cc.Node.EventType.TOUCH_START, this.DragStart, this);
        this.coinPanel.on(cc.Node.EventType.TOUCH_MOVE, this.DragMove, this);
        this.coinPanel.on(cc.Node.EventType.TOUCH_END, this.DragEnd, this);
        this.coinPanel.on(cc.Node.EventType.TOUCH_CANCEL, this.DragEnd, this);
    }

    InitTime() {
        this.time = GameData.Instance.Datas[3];
        this.dayLabel.string = (GameData.Instance.Datas[1]).toString();
        this.timeLabel.string = this.time.toString();
        this.schedule(() => {
            this.time--;
            this.timeLabel.string = this.time.toString();
            if (this.time == 0) this.Finish();
        }, 1, this.time - 1, 1);
        this.schedule(() => {
            for (let i = 0; i < this.guestLength; i++) {
                const element = this.guestDatas[i];
                if (!element) return this.SendGuest();
            }
        }, 5);
    }


    start() {
        FadePanel.Instance.node.children[0].active = true;
        FadePanel.Instance.FadeOut();
        FadePanel.Instance.node.parent.children[2].getComponent(cc.AudioSource).play();
    }

    update(dt) {
        this.CutPotatos(dt);
    }

    Point(v3: cc.Vec3) {
        var point = PoolManager.getNode(this.pointPre, this.bg);
        point.position = v3;
        point.scale = 1;
        point.opacity = 255;
        cc.tween(point)
            .to(0.5, {
                scale: 5,
                opacity: 0
            })
            .call(() => {
                PoolManager.putNode(point);
            })
            .start();
    }

    Cake() {
        if (this.isCake) return this.Point(this.panel.position);
        this.isCake = true;
        this.cake = PoolManager.getNode(this.cakePre, this.bg);
        this.cake.position = this.cakes.position;
        cc.tween(this.cake)
            .to(0.25, { position: this.panel.position })
            .start();
        this.cake.on(cc.Node.EventType.TOUCH_START, this.DragStart, this);
        this.cake.on(cc.Node.EventType.TOUCH_MOVE, this.DragMove, this);
        this.cake.on(cc.Node.EventType.TOUCH_END, this.DragEnd, this);
        this.cake.on(cc.Node.EventType.TOUCH_CANCEL, this.DragEnd, this);
    }

    NeedKnife(event: cc.Event) {
        if (this.ingredient[0] == 0 && this.isCake || event.target.name == "烤肉") this.Point(this.knife.position);
    }

    DragStart(event: cc.Event.EventTouch) {
        if (event.target == this.knife) this.knife.setPosition(this.bg.convertTouchToNodeSpaceAR(event.touch));
        else if (event.target == this.potato) {
            if (!this.cutFinish) this.CutPotato = true;
            else this.Point(this.fryMech.position);
        }
        else if (event.target == this.cake) {
            for (let i = 0; i < this.cakeItems.length; i++) {
                const element = this.cakeItems[i];
                if (element < 3) return this.NeedIngredient();
            }
            this.RollCake();
        }
        else if (event.target == this.package) {
            this.pack = PoolManager.getNode(this.packPre, this.bg);
            this.pack.angle = 0;
            this.pack.setPosition(this.bg.convertTouchToNodeSpaceAR(event.touch));
        }
        else if (this.sandwiches.children.includes(event.target)) this.sandwichPos = event.target.getPosition();
        this.GetCoin(event);
    }

    DragMove(event: cc.Event.EventTouch) {
        if (event.target == this.knife) {
            var prePos = this.knife.getPosition();
            this.knife.setPosition(this.bg.convertTouchToNodeSpaceAR(event.touch));
            if (!this.meat.getBoundingBox().contains(this.knife.getPosition())) return;
            this.dragLength += cc.Vec2.distance(this.knife.getPosition(), prePos);
            if (this.callAss) return;
            while (this.dragLength >= this.meat.height / 4) {
                this.AddIngredient(0);
                this.dragLength -= this.meat.height / 4;
                this.meat.getComponent(cc.Animation).play();
            }
        }
        else if (event.target == this.cake) {
        }
        else if (event.target == this.package) this.pack.setPosition(this.bg.convertTouchToNodeSpaceAR(event.touch));
        else if (this.sandwiches.children.includes(event.target)) event.target.setPosition(this.sandwiches.convertTouchToNodeSpaceAR(event.touch));
        this.GetCoin(event);
    }

    DragEnd(event: cc.Event.EventTouch) {
        if (event.target == this.knife) this.knife.setPosition(this.knifePos), this.dragLength = 0;
        else if (event.target == this.potato) this.CutPotato = false;
        else if (event.target == this.package) {
            if (!this.cake || !this.isRoll) {
                var pack = this.pack;
                this.pack = null;
                cc.tween(pack)
                    .by(1.5, {
                        position: cc.v3(GameData.randomRange(-300, 300), -1500),
                        angle: GameData.randomRange(-30, 30)
                    }, { easing: "quadIn" })
                    .call(() => {
                        PoolManager.putNode(pack);
                    })
                    .start();
                return;
            }
            var cakeBox = this.cake.getBoundingBoxToWorld();
            var packBox = this.pack.getBoundingBoxToWorld();
            if (!cakeBox.intersects(packBox)) {
                var pack = this.pack;
                this.pack = null;
                cc.tween(pack)
                    .by(1.5, {
                        position: cc.v3(GameData.randomRange(-300, 300), -1500),
                        angle: GameData.randomRange(-30, 30)
                    }, { easing: "quadIn" })
                    .call(() => {
                        PoolManager.putNode(pack);
                    })
                    .start();
                return;
            }
            this.PackCake();
        }
        else if (this.sandwiches.children.includes(event.target)) {
            for (let i = 0; i < this.guestLength; i++) {
                const element = this.guestDatas[i];
                if (element) {
                    var guest = (element as Guest);
                    if (guest.node.getBoundingBoxToWorld().contains(event.getLocation())) {
                        guest.GetSandwich();
                        event.target.targetOff(this);
                        PoolManager.putNode(event.target);
                    }
                }
            }
            event.target.setPosition(this.sandwichPos);
            this.sandwichPos = null;
        }
        this.GetCoin(event);
    }

    AddIngredient(num: number) {
        if (this.ingredient[num] >= 40) return;
        if (num != 3) {
            var isAdd = false;
            for (let i = 0; i < 3; i++) {
                const element = PoolManager.getNode(this.ingredientPres[num], this.bg);
                element.opacity = 255;
                element.setPosition(this.bg.convertToNodeSpaceAR(this.ingredientsAdd[num].parent.convertToWorldSpaceAR(this.ingredientsAdd[num].getPosition())));
                var pos = this.ingredients[num].position;
                var isCheese = num == 2;
                if (isCheese) element.angle = -15;
                cc.tween(element)
                    .to(0.5, {
                        position: pos,
                        angle: isCheese ? -30 : GameData.randomRange(-180, 180)
                    })
                    .call(() => {
                        if (!isAdd) isAdd = true, this.ingredient[num] = GameData.clamp(this.ingredient[num] += this.itemPart[num], 0, 40), this.CheckIngredient(num);
                    })
                    .to(isCheese ? 0 : 0.5, { opacity: 0 })
                    .call(() => {
                        PoolManager.putNode(element);
                    })
                    .start();
            }
        }
    }

    CheckIngredient(num: number) {
        console.log(this.ingredient[num]);
        if (num < 3) for (let i = 0; i < this.ingredients[num].children[0].children.length; i++) {
            const element = this.ingredients[num].children[0].children[i];
            element.active = this.ingredient[num] > 10 * i + 1;
        }
        else for (let i = 0; i < this.ingredients[num].children[0].children.length; i++) {
            const element = this.ingredients[num].children[0].children[i];
            element.active = this.ingredient[num] > 20 * i + 1;
        }
    }

    IngredientBtn(event, num: number) {
        this.AddIngredient(num);
    }

    CallAss() {
        var ani = this.assistant.getComponent(cc.Animation).play();
        if (this.callAss) ani.wrapMode = cc.WrapMode.Normal;
        else ani.wrapMode = cc.WrapMode.Reverse;
        this.callAss = !this.callAss;
    }

    CutPotatos(dt: number) {
        if (!this.cutPotato) return;
        this.cutTime += dt;
        this.cutProgress.fillRange = this.cutTime / this.potatoTime;
        if (this.cutTime >= this.potatoTime) this.CutPotato = false;
    }

    CutFinish() {
        this.cutTime = 0;
        this.cutProgress.fillRange = this.cutTime / this.potatoTime;
        this.fryMech.children[0].active = true;
        this.fringTime = this.fryTime;
        this.fryLabel.string = this.fringTime.toString();
        this.fryMech.getComponent(cc.Animation).play("Fring");
        this.schedule(this.Fring, 1, this.fryTime - 1, 1);
    }

    Fring() {
        this.fringTime--;
        this.fryLabel.string = this.fringTime.toString();
        if (this.fringTime == 0) {
            this.fryReady = true;
            this.fryMech.getComponent(cc.Animation).play("Fried");
        }
    }

    AddFry() {
        if (this.ingredient[3] >= 40) return this.Point(this.ingredients[3].position);
        if (!this.fryReady) {
            if (this.fringTime == 0) return this.Point(this.bg.convertToNodeSpaceAR(this.potato.children[0].convertToWorldSpaceAR(this.potato.children[0].children[1].position)));
            return;
        }
        this.cutFinish = false;
        this.fryReady = false;
        this.fryMech.getComponent(cc.Animation).play("Ready");
        var element = PoolManager.getNode(this.ingredientPres[3], this.bg);
        element.setPosition(this.fryMech.getPosition());
        var pos = this.ingredients[3].position;
        cc.tween(element)
            .to(0.5, { position: pos })
            .call(() => {
                GameData.clamp(this.ingredient[3] += this.itemPart[3], 0, 40);
                this.CheckIngredient(3);
                PoolManager.putNode(element);
            })
            .start();
    }

    UseIngredient(event, num: number) {
        if (GameScene.Instance.ingredient[num] == 0) {
            if (num != 3) return GameScene.Instance.Point(GameScene.Instance.bg.convertToNodeSpaceAR(GameScene.Instance.ingredientsNeed[num].parent.convertToWorldSpaceAR(GameScene.Instance.ingredientsNeed[num].position)));
            if (num == 3) {
                if (!GameScene.Instance.cutFinish) return this.Point(this.bg.convertToNodeSpaceAR(this.potato.children[0].convertToWorldSpaceAR(this.potato.children[0].children[1].position)));
                return GameScene.Instance.Point(GameScene.Instance.fryMech.position);
            }
        }
        if (!GameScene.Instance.isCake) return GameScene.Instance.Point(GameScene.Instance.cakes.position);
        if (GameScene.Instance.cakeItems[num] == 3) return;
        if (GameScene.Instance.cakeItems2[num] == 3) return;
        GameScene.Instance.ingredient[num] = GameData.clamp(GameScene.Instance.ingredient[num] -= 3, 0, 40);
        GameScene.Instance.CheckIngredient(num);
        GameScene.Instance.cakeItems2[num] += 3;
        var cakeIngred = PoolManager.getNode(GameScene.Instance.ingredientPres[num], GameScene.Instance.bg);
        cakeIngred.opacity = 255;
        cakeIngred.angle = num == 2 ? -40 : 0;
        cakeIngred.position = GameScene.Instance.ingredients[num].position;
        cc.tween(cakeIngred)
            .to(0.25, { position: GameScene.Instance.cake.position })
            .call(() => {
                PoolManager.putNode(cakeIngred);
                GameScene.Instance.cake.children[num].active = true;
                GameScene.Instance.cakeItems[num] += 3;
            })
            .start();
        GameScene.Instance.CheckRoll();
    }

    NeedIngredient() {
        for (let i = 0; i < this.cakeItems.length; i++) {
            const element = this.cakeItems[i];
            if (element < 3) this.Point(this.ingredients[i].position);
        }
    }

    CheckRoll(): boolean {
        for (let i = 0; i < this.cakeItems.length; i++) {
            const element = this.cakeItems[i];
            if (element < 3) return false;
        }
        return true;
    }

    CloseRoll() {
        if (!this.tutorial.active) return;
        this.packTur.active = true;
    }

    RollCake() {
        if (this.isRoll) return;
        if (!this.CheckRoll()) return;
        var sandwich = PoolManager.getNode(this.sandwithPre, this.bg);
        sandwich.children[0].active = false;
        sandwich.children[2].active = false;
        sandwich.setPosition(this.cake.getPosition());
        for (let i = 0; i < this.cake.children.length; i++) {
            const element = this.cake.children[i];
            element.active = false;
        }
        PoolManager.putNode(this.cake);
        this.cake = sandwich;
        this.isRoll = true;
        if (this.tutorial.active) this.CloseRoll();
    }

    PackCake() {
        PoolManager.putNode(this.pack);
        this.cake.getComponent(cc.Animation).play("Pack");
        this.cake.on(cc.Node.EventType.TOUCH_START, this.DragStart, this);
        this.cake.on(cc.Node.EventType.TOUCH_MOVE, this.DragMove, this);
        this.cake.on(cc.Node.EventType.TOUCH_END, this.DragEnd, this);
        this.cake.on(cc.Node.EventType.TOUCH_CANCEL, this.DragEnd, this);
        this.pack = null;
        this.isPackage = true;
        this.isCake = false;
        this.isRoll = false;
        this.cake.setParent(this.sandwiches);
        var index = this.sandwiches.children.indexOf(this.cake);
        this.cake.setPosition(cc.v2(0, 10 * index));
        this.cake = null;
        this.cakeItems = [0, 0, 0, 0];
        this.cakeItems2 = [0, 0, 0, 0];
        if (this.tutorial.active) {
            this.packTur.active = false;
            this.tutorial.active = false;
            this.InitTime();
            this.SendGuest();
        }
    }

    SendGuest() {
        // return;
        var num = Math.floor(GameData.randomRange(0, this.guestSummons.length));
        var guest = PoolManager.getNode(this.guestPres, this.guestPanel.children[1]);
        guest.scale = 0.6;
        guest.position = this.guestSummons[num].position;
        var num3 = Math.floor(GameData.randomRange(0, this.guestLength));
        while (this.guestDatas[num3]) {
            num3 = Math.floor(GameData.randomRange(0, this.guestLength));
        }
        var guest2 = guest.getComponent(Guest);
        guest2.num = num3;
        guest2.Walk(this.guestWaits[num3].position);
        this.guestDatas[num3] = guest2;
    }

    GuestCoin(num: number) {
        var price = GameData.Instance.Datas[4];
        while (price > 0) {
            var priceNum = Math.floor(GameData.randomRange(1, Math.min(4, price)));
            price -= priceNum;
            var coin = PoolManager.getNode(this.coinPres[priceNum - 1], this.coinPanel);
            var coinPlace = this.coinPanel.children[num].getBoundingBox();
            coin.setPosition(cc.v2(GameData.randomRange(coinPlace.xMin, coinPlace.xMax), GameData.randomRange(coinPlace.yMin, coinPlace.yMax)));
            this.coins.push(coin);
        }
        this.guestNum++;
    }

    GetCoin(event: cc.Event.EventTouch) {
        for (let i = 0; i < this.coins.length; i++) {
            const element = this.coins[i];
            var coin = element.getComponent(Coin);
            if (coin.isCollected) continue;
            var box = element.getBoundingBoxToWorld();
            var pos = event.getLocation();
            if (box.contains(pos)) coin.Collect();
        }
    }

    Finish() {
        this.unscheduleAllCallbacks();
        cc.Tween.stopAll();
        GameUI.Instance.Finish();
    }

}
